package redisDB

import (
	"bytes"
	"context"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/go-redis/redis/v8"
)

var (
	mapClient = sync.Map{}
	clientLock sync.Mutex // 连接锁定防止并发访问

	contentMap = sync.Map{}  //数据库
	contentMu sync.Mutex      //数据库锁定防止并发访问
)

//RedisContent 集合上下文
type RedisContent struct {
	client        redis.Cmdable
	repositoryMap map[string]IRedisRepository
}

//GetRedisRepository 获取集合仓储
// entity 结构体
func (r *RedisContent) GetRedisRepository(prefixName string) (IRedisRepository,bool) {
	repository, ok := r.repositoryMap[prefixName]
	return repository, ok
}

//GetRedisLock 获取分布式锁
func (r *RedisContent) GetRedisLock(lockKey string) *RedisLock {
	return NewRedisLock(r.client, lockKey)
}

//GetRedisLimit 获取限速器
func (r *RedisContent) GetRedisLimit() *RedisLimit {
	return NewRedisLimit(r.client)
}

//DataBaseMapping 添加映射
// @client 连接对象
func DataBaseMapping(mappingName string,configFn func(mappingName string) map[string]string,prefixNamesFn func()map[string]int) (*RedisContent,error) {
	value, ok := contentMap.Load(mappingName)
	if ok {
		return value.(*RedisContent), nil
	}
	contentMu.Lock()
	defer contentMu.Unlock()

	value, ok = contentMap.Load(mappingName)
	if ok {
		return value.(*RedisContent), nil
	}

	config := configFn(mappingName)
	timeout := time.Second * 30
	if timeoutStr, ok := config["timeout"]; ok {
		atoi, err := strconv.Atoi(timeoutStr)
		if err == nil {
			timeout = time.Second * time.Duration(atoi)
		}
	}
	//客户端
	client, err := GetRediClient(config)
	if err != nil {
		return nil, err
	}
	content := &RedisContent{
		client:        client,
		repositoryMap: map[string]IRedisRepository{},
	}
	prefixNames := prefixNamesFn()
	for key, _ := range prefixNames {
		content.repositoryMap[key] = &ReidsRepository{
			client:     content.client,
			entityName: key,
			timeout:    timeout,
		}
	}
	contentMap.Store(mappingName, content)
	return content, nil
}

//GetRediClient 获取客户端
func GetRediClient(config map[string]string) (redis.Cmdable, error) {
	var key bytes.Buffer
	key.WriteString(config["ipport"])
	key.WriteString("#")
	key.WriteString(config["dbname"])
	keyConn := key.String()
	client, ok := mapClient.Load(keyConn)
	if ok {
		return client.(redis.Cmdable), nil
	}
	clientLock.Lock()
	defer clientLock.Unlock()

	/*二次获取防止并发多次连接*/
	client, ok = mapClient.Load(keyConn)
	if ok {
		return client.(redis.Cmdable), nil
	}

	newClient, err := connect(config)
	if err != nil {
		return nil, err
	}
	mapClient.Store(keyConn,newClient)
	return newClient, nil
}

//connect 获取客户端
func connect(config map[string]string) (redis.Cmdable, error) {
	timeout := time.Second * 30
	if timeoutStr, ok := config["timeout"]; ok {
		atoi, err := strconv.Atoi(timeoutStr)
		if err == nil {
			timeout = time.Second * time.Duration(atoi)
		}
	}
	addr, ok := config["ipport"]
	if !ok {
		addr = "127.0.0.1:27017"
	}
	password, ok := config["password"]
	if !ok {
		password = ""
	}
	poolSize := 200
	poolSizeStr, ok := config["poolSize"]
	if ok {
		atoi, err := strconv.Atoi(poolSizeStr)
		if err == nil {
			poolSize = atoi
		}
	}
	mode := config["mode"]
	var rdb redis.Cmdable
	switch mode {
	case "Failover":
		//连接Redis哨兵模式
		rdb = redis.NewFailoverClient(&redis.FailoverOptions{
			MasterName:    "master",
			SentinelAddrs: strings.Split(addr, ","),
			Password:      password,
			PoolSize:      poolSize, // 连接池最大socket连接数，默认为4倍CPU数， 4 * runtime.NumCPU
			MinIdleConns:  10,       //在启动阶段创建指定数量的Idle连接，并长期维持idle状态的连接数不少于指定数量；
		})
		break
	case "Cluster":
		//连接Redis集群模式
		rdb = redis.NewClusterClient(&redis.ClusterOptions{
			Addrs:        strings.Split(addr, ","),
			Password:     password,
			PoolSize:     poolSize, // 连接池最大socket连接数，默认为4倍CPU数， 4 * runtime.NumCPU
			MinIdleConns: 10,       //在启动阶段创建指定数量的Idle连接，并长期维持idle状态的连接数不少于指定数量；
		})
		break
	default:
		// 普通单机模式连接
		rdb = redis.NewClient(&redis.Options{
			Addr:     addr,
			Password: password, // no password set
			PoolSize:     poolSize, // 连接池最大socket连接数，默认为4倍CPU数， 4 * runtime.NumCPU
			MinIdleConns: 10,       //在启动阶段创建指定数量的Idle连接，并长期维持idle状态的连接数不少于指定数量；
		})
		break
	}
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	_, err := rdb.Ping(ctx).Result()

	if err != nil {
		return nil, err
	}
	return rdb, nil
}

//// 初始化连接(连接Redis哨兵模式)
//func initClientSentry() (err error) {
//	rdb := redis.NewFailoverClient(&redis.FailoverOptions{
//		MasterName:    "master",
//		SentinelAddrs: []string{"x.x.x.x:26379", "xx.xx.xx.xx:26379", "xxx.xxx.xxx.xxx:26379"},
//	})
//	_, err = rdb.Ping().Result()
//	if err != nil {
//		return err
//	}
//	return nil
//}

////初始化连接(连接Redis集群模式)
//func initClientCluster() (err error) {
//	rdb := redis.NewClusterClient(&redis.ClusterOptions{
//		Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
//	})
//	_, err = rdb.Ping().Result()
//	if err != nil {
//		return err
//	}
//	return nil
//}
